function getBollMd (dataList, ma) {
    const dataSize = dataList.length
    let sum = 0
    dataList.forEach(data => {
        const closeMa = data.close - ma
        sum += closeMa * closeMa
    })
    sum = Math.abs(sum)
    return Math.sqrt(sum / dataSize)
}

export const BAND_STATE = {
    OPENING: 1,
    PARALLEL: 0,
    CLOSING: -1
}

const STATE_TEXT_TO_VALUE = {
    opening: BAND_STATE.OPENING,
    parallel: BAND_STATE.PARALLEL,
    closing: BAND_STATE.CLOSING
}

const OPEN_THRESHOLD = 0.1
const CLOSE_THRESHOLD = -0.1
const PARALLEL_WIDTH_THRESHOLD = 0.03
const PARALLEL_SLOPE_THRESHOLD = 0.015
const SLOPE_MIN_FOR_TREND = 0.02
const HYSTERESIS_MARGIN = 0.02
const OPEN_CLOSE_CONFIRM_COUNT = 2

export function calculateBollSeries (dataList, params) {
    const [period, multiplier] = params
    const p = period - 1
    let closeSum = 0
    let closeSumMa5 = 0
    const results = []
    let confirmedState = null
    let pendingState = null
    let pendingCount = 0

    dataList.forEach((kLineData, i) => {
        const close = kLineData.close
        const boll = {}
        closeSum += close
        closeSumMa5 += close
        if (i >= p) {
            boll.mid = closeSum / period
            const md = getBollMd(dataList.slice(i - p, i + 1), boll.mid)
            boll.up = boll.mid + multiplier * md
            boll.dn = boll.mid - multiplier * md
            const bandWidth = boll.up - boll.dn
            boll.bandWidth = bandWidth
            const prev = results[i - 1]
            if (prev && prev.bandWidth != null && prev.up != null && prev.dn != null) {
                const prevWidth = prev.bandWidth
                const widthChangeRatio = prevWidth === 0 ? 0 : (bandWidth - prevWidth) / prevWidth
                const upperSlope = boll.up - prev.up
                const lowerSlope = boll.dn - prev.dn
                const slopeAvgRatio = prevWidth === 0 ? 0 : (Math.abs(upperSlope) + Math.abs(lowerSlope)) / (2 * prevWidth)
                const prevSmoothedWidthChange = prev.smoothedWidthChange ?? widthChangeRatio
                const smoothedWidthChange = prevSmoothedWidthChange * 0.7 + widthChangeRatio * 0.3
                boll.widthChange = widthChangeRatio
                boll.smoothedWidthChange = smoothedWidthChange

                let rawState = confirmedState ?? 'parallel'
                if (smoothedWidthChange > OPEN_THRESHOLD && slopeAvgRatio > SLOPE_MIN_FOR_TREND) {
                    rawState = 'opening'
                } else if (smoothedWidthChange < CLOSE_THRESHOLD && slopeAvgRatio > SLOPE_MIN_FOR_TREND) {
                    rawState = 'closing'
                } else if (Math.abs(smoothedWidthChange) <= PARALLEL_WIDTH_THRESHOLD && slopeAvgRatio <= PARALLEL_SLOPE_THRESHOLD) {
                    rawState = 'parallel'
                } else if (confirmedState) {
                    rawState = confirmedState
                } else {
                    rawState = 'parallel'
                }

                if (confirmedState === 'parallel' && rawState !== 'parallel' && Math.abs(smoothedWidthChange) < OPEN_THRESHOLD + HYSTERESIS_MARGIN) {
                    rawState = 'parallel'
                }

                let nextState = confirmedState ?? 'parallel'
                if (rawState === confirmedState) {
                    pendingState = null
                    pendingCount = 0
                    nextState = rawState
                } else {
                    if (pendingState === rawState) {
                        pendingCount += 1
                    } else {
                        pendingState = rawState
                        pendingCount = 1
                    }
                    const confirmNeed = rawState === 'parallel' ? 1 : OPEN_CLOSE_CONFIRM_COUNT
                    if (pendingCount >= confirmNeed) {
                        nextState = rawState
                        pendingState = null
                        pendingCount = 0
                    }
                }

                confirmedState = nextState
                boll.bandStateText = nextState
                boll.bandState = nextState ? STATE_TEXT_TO_VALUE[nextState] : null
            } else {
                boll.bandState = null
                boll.bandStateText = null
            }
            closeSum -= dataList[i - p].close
        }

        let ma5 = close
        if (i >= 5 - 1) {
            ma5 = closeSumMa5 / 5
            closeSumMa5 -= dataList[i - (5 - 1)].close
        }

        boll.ma5 = ma5
        results.push(boll)
    })

    return results
}

const BREAKOUT_PARAMS = [55, 1]

const stoch = {
    name: 'BOLLMA',
    shortName: 'BOLLMA',
    calcParams: [20, 2],
    figures: [
        { key: 'up', title: 'UP: ', type: 'line' },
        { key: 'mid', title: 'MID: ', type: 'line' },
        { key: 'dn', title: 'DN: ', type: 'line' },
        { key: 'ma5', title: 'MA5: ', type: 'line' }
    ],
    calc: (dataList, indicator) => {
        return calculateBollSeries(dataList, indicator.calcParams)
    }
}

export default stoch
